#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>

#include <string.h>
using namespace std;
typedef struct
{
    float x,y,z,w;
}Vector4f;


typedef struct
{
    float a,b,c,d;
}Quaternion;

Vector4f	P1;
Vector4f   	P2;
Vector4f   	P3;
Vector4f   	isoBarycentre;

int quaternion;

float rotationx;
float rotationy;
float rotationz;

float * toArray(Vector4f v)
{
	float * fl=new float[4];
	fl[0]=v.x;
	fl[1]=v.y;
	fl[2]=v.z;
	fl[3]=v.w;
	
	return fl;
}
/*
Vector4f	P1= Vector4(80,-20,150,1);
Vector4f   	P2= Vector4(-80,-20,150,1);
Vector4f   	P3= Vector4(0, 90, 150,1);*/

float * RotationMatrix(Quaternion q)
{
    float * matriceRotation = new float[16];

    float xx      =  q.a * q.a;
    float xy      =  q.a * q.b;
    float xz      =  q.a * q.c;
    float xw      =  q.a * q.d;

    float yy      = q.b * q.b;
    float yz      = q.b * q.c;
    float yw      = q.b * q.d;

    float zz      = q.c * q.c;
    float zw      = q.c * q.d;

    matriceRotation[0]  = 1 - 2 * ( yy + zz );
    matriceRotation[1]  =     2 * ( xy - zw );
    matriceRotation[2]  =     2 * ( xz + yw );

    matriceRotation[4]  =     2 * ( xy + zw );
    matriceRotation[5]  = 1 - 2 * ( xx + zz );
    matriceRotation[6]  =     2 * ( yz - xw );

    matriceRotation[8]  =     2 * ( xz - yw );
    matriceRotation[9]  =     2 * ( yz + xw );
    matriceRotation[10] = 1 - 2 * ( xx + yy );

    matriceRotation[3]  = matriceRotation[7] = matriceRotation[11] = matriceRotation[12] = matriceRotation[13] = matriceRotation[14] = 0;
    matriceRotation[15] = 1;

    return matriceRotation;

}

Quaternion norme1(Quaternion q)
{
    Quaternion qres;
    qres.a = pow(q.a,2) + pow(q.b,2) + pow(q.c,2) + pow(q.d,2);
    qres.b = 0;
    qres.c = 0;
    qres.d = 0;
    return qres;
}



Quaternion multiplication(Quaternion q1,Quaternion q2)
{
    Quaternion qres;
    qres.a = q1.a*q2.a - q1.b*q2.b - q1.c*q2.c - q1.d*q2.d;
    qres.b = q1.a*q2.b + q1.b*q2.a + q1.c*q2.d - q1.d*q2.c;
    qres.c = q1.a*q2.c + q1.c*q2.a + q1.d*q2.b - q1.b*q2.d;
    qres.d = q1.a*q2.d + q1.d*q2.a + q1.b*q2.c - q1.c*q2.b;
    return qres;
}

Quaternion conjuge(Quaternion q)
{
    Quaternion qres;
    qres.a = q.a;
    qres.b = -q.b;
    qres.c = -q.c;
    qres.d = -q.d;
    return qres;
}

Quaternion norme(Quaternion q)
{
    return multiplication(conjuge(q),conjuge(q));
}

Quaternion oppose(Quaternion q)
{
    Quaternion qres;
    qres.a = -q.a;
    qres.b = -q.b;
    qres.c = -q.c;
    qres.d = -q.d;
    return qres;
}

Quaternion norme2(Quaternion q)
{
    return multiplication(q,conjuge(q));
}

Quaternion add(Quaternion q1, Quaternion q2)
{
    Quaternion qres;
    qres.a = q1.a + q2.a;
    qres.b = q1.b + q2.b;
    qres.c = q1.c + q2.c;
    qres.d = q1.d + q2.d;
    return qres;
}

Quaternion sub(Quaternion q1, Quaternion q2)
{
    return add(q1,oppose(q2));
}

double qlen(Quaternion p)
{
    return sqrt(p.a*p.a + p.b*p.b + p.c*p.c + p.d*p.d);
}

Quaternion versor(Quaternion q)
{
    double l=qlen(q);
    q.a/=l;
    q.b/=l;
    q.c/=l;
    q.d/=l;
    return q;
}

Quaternion inv(Quaternion q)
{
    Quaternion qres;
    double l = q.a * q.a + q.b * q.b + q.c * q.c + q.d * q.d;
    qres.a =  q.a/l;
    qres.b = -q.b/l;
    qres.c = -q.c/l;
    qres.d = -q.d/l;
    return qres;
}


Quaternion divDroite(Quaternion q1, Quaternion q2)
{
    return multiplication(inv(q1),q2);
}

Quaternion divGauche(Quaternion q1, Quaternion q2)
{
    return multiplication(q1,inv(q2));
}

void aff(Quaternion q)
{
    printf("%f,",q.a);
    printf("%f,",q.b);
    printf("%f,",q.c);
    printf("%f\n",q.d);
}



void fillMatrixTranslation(float * mat, float x, float y, float z)
{
    mat[0]=1;   mat[1]=0;   mat[2]=0;   mat[3]=x;
    mat[4]=0;   mat[5]=1;   mat[6]=0;   mat[7]=y;
    mat[8]=0;   mat[9]=0;   mat[10]=1;  mat[11]=z;
    mat[12]=0;  mat[13]=0;  mat[14]=0;  mat[15]=1;
}

void fillMatrixRotationx(float * mat, float rot)
{
    mat[0]=1;           mat[1]=0;           mat[2]=0;           mat[3]=0;
    mat[4]=0;           mat[5]=cos(rot);  mat[6]=-sin(rot); mat[7]=0;
    mat[8]=0;           mat[9]=sin(rot);  mat[10]=cos(rot); mat[11]=0;
    mat[12]=0;          mat[13]=0;          mat[14]=0;          mat[15]=1;
}

void fillMatrixRotationy(float * mat, float rot)
{
    mat[0]=cos(rot);  mat[1]=-sin(rot); mat[2]=0;           mat[3]=0;
    mat[4]=sin(rot);  mat[5]=cos(rot);  mat[6]=0;           mat[7]=0;
    mat[8]=0;           mat[9]=0;           mat[10]=1;          mat[11]=0;
    mat[12]=0;          mat[13]=0;          mat[14]=0;          mat[15]=1;
}

void fillMatrixRotationz(float * mat, float rot)
{
    mat[0]=cos(rot);  mat[1]=0;           mat[2]=sin(rot);          mat[3]=0;
    mat[4]=0;           mat[5]=1;           mat[6]=0;                   mat[7]=0;
    mat[8]=-sin(rot); mat[9]=0;           mat[10]=cos(rot);         mat[11]=0;
    mat[12]=0;          mat[13]=0;          mat[14]=0;                  mat[15]=1;
}

float * multMatrix(float * m1, float * m2)
{
    float * mo= new float[16];  //  mo= matrix output = la matrice de sortie

    for(int j=0;j<4;j++)
    {
        for(int i=0;i<4;i++)
        {
             mo[j*4 +i]= (m1[j*4+ 0]*m2[0+i])+(m1[j*4+1]*m2[4+i])+(m1[j*4+2]*m2[8+i])+(m1[j*4+3]*m2[12+i]);
        }
    }

    return mo;
}



float * multMatrixVector4(float *m, float * v)
{
    float * vo= new float[4];   //  vo pour vector output;

    for(int j=0;j<4;j++)
    {
        vo[j]=  (m[j*4+ 0]*v[0])+(m[j*4+1]*v[1])+(m[j*4+2]*v[2])+(m[j*4+3]*v[3]);
    }

    return vo;
}

void fillProjection(float *mat)
{
    mat[0]=135;     mat[1]=0;       mat[2]=0;       mat[3]=0;
    mat[4]=0;       mat[5]=135;     mat[6]=0;       mat[7]=0;
    mat[8]=0;       mat[9]=0;       mat[10]=0;      mat[11]=0;
    mat[12]=0;      mat[13]=0;      mat[14]=1;      mat[15]=135;
}



GLvoid initGL()
{
	glClearColor(0.0, 0.0, 0.0, 1.0);
    glEnable(GL_DEPTH_TEST);

}

void affiche_scene()
{
	glLoadIdentity();
	glutSwapBuffers();							// Affiche la scène à l'écran (affichage en double buffer)
}

GLvoid callback_display()
{
	glMatrixMode(GL_MODELVIEW);
 
	// On efface les pixels de l'image (color buffer) et le Z-Buffer (depth buffer).
	
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   
    isoBarycentre.x=(P1.x+P2.x+P3.x)/3.0;
    isoBarycentre.y=(P1.y+P2.y+P3.y)/3.0;
    isoBarycentre.z=(P1.z+P2.z+P3.z)/3.0;
    
    glPushMatrix();

       float matrixProjection[16];

        float mth[16];                  //matrice de translation en coordonnées homogènes

        float mrx[16];
        float mry[16];
        float mrz[16];

        float * mato;

        float * Prime1;
        float * Prime2;
        float * Prime3;

        Vector4f inverseBarycentre;
        inverseBarycentre.x=-isoBarycentre.x;
        inverseBarycentre.y=-isoBarycentre.y;
        inverseBarycentre.z=-isoBarycentre.z;

        fillMatrixTranslation(mth,isoBarycentre.x,isoBarycentre.y,isoBarycentre.z);
        fillProjection(matrixProjection);

        if(quaternion==0)
        {
            fillMatrixRotationx(mrx,rotationx);
            fillMatrixRotationy(mry,rotationy);
            fillMatrixRotationz(mrz,rotationz);

            mato=multMatrix(matrixProjection,mth);
            mato=multMatrix(mato,mrx);
            mato=multMatrix(mato,mry);
            mato=multMatrix(mato,mrz);

            fillMatrixTranslation(mth,inverseBarycentre.x,inverseBarycentre.y,inverseBarycentre.z);

            mato=multMatrix(mato,mth);
        }
        else
        {
            float * rotmat;

            Quaternion  quatX;

            quatX.a=cos(rotationx/2.0);
            quatX.b=sin(rotationx/2.0);
            quatX.c=0;
            quatX.d=0;

            Quaternion  quatY;

            quatY.a=cos(rotationy/2.0);
            quatY.b=0;
            quatY.c=sin(rotationy/2.0);
            quatY.d=0;

            Quaternion  quatZ;

            quatZ.a=cos(rotationz/2.0);
            quatZ.b=0;
            quatZ.c=0;
            quatZ.d=sin(rotationz/2.0);

            Quaternion  resultat;

            resultat=multiplication(quatX,quatY);
            resultat=multiplication(resultat,quatZ);

            rotmat=RotationMatrix(resultat);

            mato=multMatrix(matrixProjection,mth);
            mato=multMatrix(mato,rotmat);

            fillMatrixTranslation(mth,inverseBarycentre.x, inverseBarycentre.y, inverseBarycentre.z);

            mato=multMatrix(mato,mth);
        }

        Prime1=multMatrixVector4(mato,toArray(P1));
        Prime2=multMatrixVector4(mato,toArray(P2));
        Prime3=multMatrixVector4(mato,toArray(P3));

        Prime1[0]=Prime1[0]/Prime1[3];
        Prime1[1]=Prime1[1]/Prime1[3];
        Prime1[2]=0;
        Prime1[3]=1;

        Prime2[0]=Prime2[0]/Prime2[3];
        Prime2[1]=Prime2[1]/Prime2[3];
        Prime2[2]=0;
        Prime2[3]=1;

        Prime3[0]=Prime3[0]/Prime3[3];
        Prime3[1]=Prime3[1]/Prime3[3];
        Prime3[2]=0;
        Prime3[3]=1;

        glBegin(GL_TRIANGLES);

            glColor3f(1.0,0.0,0.0);
            glVertex4fv(Prime1);
            glColor3f(0.0,1.0,0.0);
            glVertex4fv(Prime2);
            glColor3f(0.0,0.0,1.0);
            glVertex4fv(Prime3);

        glEnd();

    glPopMatrix();
    
	glFlush();
}


GLvoid callback_reshape(GLsizei width, GLsizei height)
{
	glViewport(0, 0, width, height);
	
	glLoadIdentity();
	glOrtho(-400,400,-400,400,-10,10);

	glMatrixMode(GL_MODELVIEW);
}

GLvoid callback_keyboard(unsigned char key, int x, int y)
{
	{
	    switch(key)
	    {
        case 'a':
        rotationx+=0.1;
        glutPostRedisplay();
        break;
        case 'z':
        rotationx-=0.1;
        glutPostRedisplay();
        break;
        
         case 'q':
        rotationy+=0.1;
        glutPostRedisplay();
        break;
        case 's':
        rotationy-=0.1;
        glutPostRedisplay();
        break;
        
         case 'w':
        rotationz+=0.1;
        glutPostRedisplay();
        break;
        case 'x':
        rotationz-=0.1;
        glutPostRedisplay();
        break;
        
        
        case 'r':
        if(quaternion==1)
        {
			quaternion=0;
		}
		else
		{	
			quaternion=1;
		}
        glutPostRedisplay();
        break;
	    }
	}
}

GLvoid callback_special(int key, int x, int y)
{
}

GLvoid callback_mouse(int button, int state, int x, int y)
{

}

GLvoid callback_motion(int x, int y)
{
}


int main(int argc, char *argv[])
{

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE | GLUT_DEPTH);
	glutInitWindowSize(600, 600);
	glutInitWindowPosition(0, 0);
	glutCreateWindow("Espaces projectifs");

	// Intitialisation de paramètres d'OpenGL
	
	initGL();

	 rotationx=0;
	 rotationy=0;
	 rotationz=0;
	 quaternion=1;
	 
	 
	P1.x=80;
	P1.y=-20;
	P1.z=150;
	P1.w=1;
	
	P2.x=-80;
	P2.y=-20;
	P2.z=150;
	P2.w=1;
	
	P3.x=0;
	P3.y=90;
	P3.z=150;
	P3.w=1;


	// Déclaration des fonctions de call-back.
	
	glutDisplayFunc(&callback_display);
	glutReshapeFunc(&callback_reshape);
	glutKeyboardFunc(&callback_keyboard);
	glutSpecialFunc(&callback_special);
	glutMouseFunc(&callback_mouse);
	glutMotionFunc(&callback_motion);

	// Boucle principale de Glut.
	glutMainLoop();

	// Code de retour
	return 1;
}
